﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Configuration;
using DaCredito;

namespace DaCredito
{
    /// <summary>
    /// Classe base para objetos de configuração.
    /// </summary>
    public abstract class AbstractSettings
    {

        // Repositório das propriedades
        private PropertyList ioItems;

        /// <summary>
        /// Construtor.
        /// </summary>
        /// <param name="aoItems">Objeto PropertyList.</param>
        public AbstractSettings(PropertyList aoItems)
        {
            this.ioItems = new PropertyList();
            if (aoItems != null)
                this.ioItems.AddRange(aoItems);
            this.InitSettings();
            this.ProcessAttributes();
        }

        /// <summary>
        /// Constrói o Settings a partir de um arquivo, de nome Settings.xml, 
        /// localizado no mesmo diretório do assembly requisitado.
        /// </summary>
        /// <param name="aoObject">Objeto indicando a origem do arquivo.</param>
        public AbstractSettings(Assembly aoAssembly)
            : this(aoAssembly, "Settings.xml")
        {
        }

        /// <summary>
        /// Constrói o Settings a partir de um arquivo localizado no mesmo diretório do
        /// assembly requisitado.
        /// </summary>
        /// <param name="aoObject">Objeto indicando a origem do arquivo.</param>
        /// <param name="asFileName">Nome do arquivo de propriedades.</param>
        public AbstractSettings(Assembly aoAssembly, string asFileName)
        {
            Uri loCodeBase = new Uri(aoAssembly.CodeBase);
            string lsFulName = string.Format(@"{0}\{1}",
              new FileInfo(loCodeBase.LocalPath).Directory.FullName, asFileName);
            this.ioItems = new PropertyList();
            this.InitSettings();
            this.ProcessAttributes();
            //this.LoadPropertiesFromFile(lsFulName);
        }

        /// <summary>
        /// Instancia o objeto criando a lista de propriedades com os
        /// valores da seção <c>asSectionName</c> do arquivo de configuração
        /// da aplicação.
        /// </summary>
        /// <param name="asSectionName">
        /// Nome da seção do arquivo de configuração da aplicação.
        /// </param>
        public AbstractSettings(string asSectionName)
            : this(PropertyList.GetSection(asSectionName))
        {
        }

        /// <summary>
        /// Instancia o objeto criando a lista de propriedades com os
        /// valores recuperados do arquivo <code>aoFile</code>.
        /// </summary>
        /// <param name="aoFile">Informações do arquivo a ser carregado.</param>
        public AbstractSettings(FileInfo aoFile)
        {
            this.ioItems = new PropertyList();
            this.InitSettings();
            this.ProcessAttributes();
           // this.LoadPropertiesFromFile(aoFile.FullName);
        }

        #region Public Members

        /// <summary>
        /// Retorna o objeto PropertyList associado à classe.
        /// </summary>
        public PropertyList Items
        {
            get { return this.ioItems; }
        }

        /// <summary>
        /// Retorna os valores padrões das propriedades.
        /// </summary>
        public PropertyList.PropertyListDefaults Defaults
        {
            get { return this.ioItems.Defaults; }
        }

        /// <summary>
        /// Retorna o objeto onde são armazenados os nomes das propriedades não serializáveis.
        /// </summary>
        public List<string> NonSerialized
        {
            get { return this.ioItems.NonSerialized; }
        }

        /// <summary>
        /// Retorna a lista de propriedades no formato de string.
        /// </summary>
        /// <returns>A lista de propriedades no formato de string.</returns>
        public override string ToString()
        {
            return this.ioItems.ToString();
        }

        /// <summary>
        /// Verifica se existe um valor armazenado para a propriedade e, no caso
        /// de seu tipo ser uma string, se o valor é uma string vazia ("").
        /// </summary>
        /// <param name="asKey">Nome da propriedade.</param>
        /// <returns>
        /// True se não existe um valor armazenado para a propriedade ou se
        /// o valor é uma string vazia.
        /// </returns>
        public bool IsEmpty(string asKey)
        {
            return this.ioItems.IsEmpty(asKey);
        }

        /// <summary>
        /// Carrega as propriedades serializadas de um arquivo.
        /// </summary>    
        /// <param name="asFileName">Nome do arquivo a ser lido.</param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
       /* public bool LoadPropertiesFromFile(string asFileName)
        {
            return this.LoadPropertiesFromFile(asFileName, null);
        }*/

        /// <summary>
        /// Carrega as propriedades serializadas de um arquivo.
        /// </summary>    
        /// <param name="asFileName">Nome do arquivo a ser lido.</param>
        /// <param name="aoHandler">
        /// Handler para o evento DeserializeValue. Pode ser null.
        /// </param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
      /*  public bool LoadPropertiesFromFile(string asFileName, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            XmlPropertySerializer loSerializer = new XmlPropertySerializer(this.ioItems);
            if (aoHandler != null)
                loSerializer.DeserializeValue += aoHandler;
            // Tenta ler do arquivo
            if (File.Exists(asFileName))
            {
                loSerializer.Load(asFileName);
                return true;
            }
            return false;
        }*/

        /// <summary>
        /// Carrega as propriedades serializadas de um arquivo, adicionando-as à lista existente.
        /// </summary>    
        /// <param name="asFileName">Nome do arquivo a ser lido.</param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
        public bool AddPropertiesFromFile(string asFileName)
        {
            return true;// this.AddPropertiesFromFile(asFileName, null);
        }

        /// <summary>
        /// Carrega as propriedades serializadas de um arquivo, adicionando-as à lista existente.
        /// </summary>    
        /// <param name="asFileName">Nome do arquivo a ser lido.</param>
        /// <param name="aoHandler">
        /// Handler para o evento DeserializeValue. Pode ser null.
        /// </param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
        /*public bool AddPropertiesFromFile(string asFileName, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            PropertyList loNewItems = new PropertyList();
            XmlPropertySerializer loSerializer = new XmlPropertySerializer(loNewItems);
            if (aoHandler != null)
                loSerializer.DeserializeValue += aoHandler;
            // Tenta ler do arquivo
            if (File.Exists(asFileName))
            {
                loSerializer.Load(asFileName);
                this.ioItems.AddRange(loNewItems);
                return true;
            }
            return false;
        }*/

        /// <summary>
        /// Carrega as propriedades serializadas em um recurso de um assembly.
        /// </summary>
        /// <param name="asResourceName">Nome do recurso.</param>
        /// <param name="aoAssembly">Assembly onde o recurso está armazenado.</param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
        public bool LoadPropertiesFromResource(string asResourceName, Assembly aoAssembly)
        {
            return true; //return this.LoadPropertiesFromResource(null, asResourceName, aoAssembly, null);
        }

        /// <summary>
        /// Carrega as propriedades serializadas em um recurso de um assembly.
        /// </summary>
        /// <param name="asResourceName">Nome do recurso.</param>
        /// <param name="aoAssembly">Assembly onde o recurso está armazenado.</param>
        /// <param name="aoHandler">
        /// Handler para o evento DeserializeValue. Pode ser null.
        /// </param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
     /*   public bool LoadPropertiesFromResource(string asResourceName,
          Assembly aoAssembly, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            return this.LoadPropertiesFromResource(null, asResourceName, aoAssembly, aoHandler);
        }*/

        /// <summary>
        /// Carrega as propriedades serializadas em um recurso de um assembly.
        /// </summary>
        /// <param name="atType">Tipo cujo o namespace será usado como escopo para descobrir o recurso.</param>    
        /// <param name="asResourceName">Nome do recurso.</param>
        /// <param name="aoAssembly">Assembly onde o recurso está armazenado.</param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
       /* public bool LoadPropertiesFromResource(Type atType, string asResourceName, Assembly aoAssembly)
        {
            return this.LoadPropertiesFromResource(atType, asResourceName, aoAssembly, null);
        }*/

        /// <summary>
        /// Carrega as propriedades serializadas em um recurso de um assembly.
        /// </summary>
        /// <param name="atType">Tipo cujo o namespace será usado como escopo para descobrir o recurso.</param>    
        /// <param name="asResourceName">Nome do recurso.</param>
        /// <param name="aoAssembly">Assembly onde o recurso está armazenado.</param>
        /// <param name="aoHandler">
        /// Handler para o evento DeserializeValue. Pode ser null.
        /// </param>
        /// <returns>True se bem sucedido, false caso contrário.</returns>
       /* public bool LoadPropertiesFromResource(Type atType, string asResourceName,
          Assembly aoAssembly, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            XmlPropertySerializer loSerializer = new XmlPropertySerializer(this.ioItems);
            if (aoHandler != null)
                loSerializer.DeserializeValue += aoHandler;
            return loSerializer.LoadResource(atType, asResourceName, aoAssembly);
        }
        */
        /// <summary>
        /// Salva as propriedades definidas no objeto para arquivo.
        /// </summary>
        /// <remarks>
        /// Se o arquivo representado por <code>asFileName</code> já existir,
        /// ele será sobrescrito.
        /// </remarks>
        /// <param name="asFileName">
        /// Nome do arquivo onde as propriedades serão salvas.
        /// </param>
      /* public void SavePropertiesToFile(string asFileName)
        {
            this.SavePropertiesToFile(asFileName, null);
        }*/

        /// <summary>
        /// Salva as propriedades definidas no objeto para arquivo.
        /// </summary>
        /// <remarks>
        /// Se o arquivo representado por <code>asFileName</code> já existir,
        /// ele será sobrescrito.
        /// </remarks>
        /// <param name="asFileName">
        /// Nome do arquivo onde as propriedades serão salvas.
        /// </param>
        /// <param name="aoHandler">
        /// Handler para o evento SerializeValue. Pode ser null.
        /// </param>
       /* public void SavePropertiesToFile(string asFileName, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            FileInfo loInfo = new FileInfo(asFileName);
            if (loInfo.Exists)
                loInfo.Delete();
            using (StreamWriter loWriter = new StreamWriter(loInfo.OpenWrite()))
                SavePropertiesToStream(loWriter, aoHandler);
        }*/

        /// <summary>
        /// Salva as propriedades definidas no objeto para um objeto Stream.
        /// </summary>
        /// <param name="aoStream">
        /// Objeto StreamWriter que irá armazenar as propriedades.
        /// </param>
        /// <param name="aoHandler">
        /// Handler para o evento SerializeValueEvent. Pode ser null.
        /// </param>
       /* public void SavePropertiesToStream(StreamWriter aoStream, EventHandler<SerializeValueEventArgs> aoHandler)
        {
            XmlPropertySerializer loSerializer = new XmlPropertySerializer(this.Items);
            if (aoHandler != null)
                loSerializer.SerializeValue += aoHandler;
            loSerializer.Save(aoStream);
        }*/

        /// <summary>
        /// Para o texto recebido, substitui as propriedades encontradas entre chaves
        /// pelo seu valor correspondente armazenado neste Settings.
        /// </summary>
        /// <remarks>
        /// <para>Este método apenas expande as propriedades que estejam armazenadas
        /// nele. Caso o texto contenha uma propriedade que não seja encontrada,
        /// a expressão continuará inalterada.</para>
        /// <para>Se o valor da propriedade for nula, a expressão entre chaves
        /// será apagada do texto.</para>
        /// </remarks>
        /// <param name="asText">Texto a ser expandido.</param>
        /// <returns>
        /// O texto expandido com os valores das propriedades encontradas.
        /// </returns>
        public string Expand(string asText)
        {
            return this.Expand(asText, null);
        }

        /// <summary>
        /// Para o texto recebido, substitui as propriedades encontradas entre chaves
        /// pelo seu valor correspondente armazenado neste Settings.
        /// </summary>
        /// <remarks>
        /// <para>Este método apenas expande as propriedades que estejam armazenadas
        /// nele. Caso o texto contenha uma propriedade que não seja encontrada,
        /// a expressão continuará inalterada.</para>
        /// <para>Se o valor da propriedade for nula, a expressão entre chaves
        /// será apagada do texto.</para>
        /// </remarks>
        /// <param name="asText">Texto a ser expandido.</param>
        /// <param name="provider">Fornecedor dos formatos dos objetos.</param>
        /// <returns>
        /// O texto expandido com os valores das propriedades encontradas.
        /// </returns>
        public string Expand(string asText, IFormatProvider provider)
        {
            foreach (string lsKey in this.ioItems.Keys)
            {
                string lsName = "{" + lsKey + "}";
                if (asText.Contains(lsName))
                {
                    object loValue = this.ioItems[lsKey];
                    if (loValue != null)
                    {
                        if (provider != null)
                            asText = asText.Replace(lsName, Convert.ToString(loValue, provider));
                        else
                            asText = asText.Replace(lsName, Convert.ToString(loValue));
                    }
                    else
                        asText = asText.Replace(lsName, "");
                }
            }
            return asText;
        }

        #endregion // Public Members


        #region Protected Members

        /// <summary>
        /// Metódo de inicialização para as classes derivadas.
        /// </summary>
        protected abstract void InitSettings();

        #endregion  // Protected Members


        #region Private Members

        /// <summary>
        /// Processa os atributos da classe.
        /// </summary>
        private void ProcessAttributes()
        {
            Type loType = this.GetType();
            while (loType != typeof(AbstractSettings))
            {
                Type[] ltNestedTypes = loType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Public);
                foreach (Type ltNested in ltNestedTypes)
                {
                    string lsNestedName;
                    int liPos = ltNested.Name.IndexOf("Settings");
                    if (liPos > 0)
                        lsNestedName = ltNested.Name.Substring(0, liPos);
                    else
                        lsNestedName = ltNested.Name;
                    this.ProcessProperties(lsNestedName, ltNested.GetProperties(BindingFlags.Instance | BindingFlags.Public));
                }
                this.ProcessProperties("", loType.GetProperties(BindingFlags.Instance | BindingFlags.Public));
                loType = loType.BaseType;
            }
        }

        private void ProcessProperties(string asName, PropertyInfo[] aoProps)
        {
            if (!string.IsNullOrEmpty(asName))
                asName += ".";
            foreach (PropertyInfo loProp in aoProps)
            {
                object[] loAttributes = loProp.GetCustomAttributes(false);
                foreach (object loAttr in loAttributes)
                {
                    if (loAttr is DefaultSettingValueAttribute)
                    {
                        string lsValue = ((DefaultSettingValueAttribute)loAttr).Value;
                        this.Defaults[asName + loProp.Name] = lsValue;
                    }
                    else// if (loAttr is NonSerializedPropertyAttribute)
                        this.NonSerialized.Add(asName + loProp.Name);
                }
            }
        }

        #endregion  // Private Members

    } // BaseSettings

} // Trends.TFC.Common
